package com.hsurosy.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import com.alibaba.fastjson.JSON;
import com.hsurosy.domain.award.model.aggregate.GiveOutPrizesAggregate;
import com.hsurosy.domain.award.model.aggregate.UserAwardRecordAggregate;
import com.hsurosy.domain.award.model.entity.TaskEntity;
import com.hsurosy.domain.award.model.entity.UserAwardRecordEntity;
import com.hsurosy.domain.award.model.entity.UserCreditAwardEntity;
import com.hsurosy.domain.award.model.valobj.AccountStatusVO;
import com.hsurosy.domain.award.repository.IAwardRepository;
import com.hsurosy.infrastructure.event.EventPublisher;
import com.hsurosy.infrastructure.persistent.dao.*;
import com.hsurosy.infrastructure.persistent.po.Task;
import com.hsurosy.infrastructure.persistent.po.UserAwardRecord;
import com.hsurosy.infrastructure.persistent.po.UserCreditAccount;
import com.hsurosy.infrastructure.persistent.po.UserRaffleOrder;
import com.hsurosy.infrastructure.persistent.redis.IRedisService;
import com.hsurosy.types.common.Constants;
import com.hsurosy.types.enums.ResponseCode;
import com.hsurosy.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @Author Hsu琛君珩
 * @Date 2024-11-14 11:26
 * @Description 奖品仓储服务
 * @Version: v1.0.0
 */
@Slf4j
@Repository
public class AwardRepository implements IAwardRepository {

    @Resource
    private ITaskDao taskDao; // 任务数据访问层
    @Resource
    private IUserAwardRecordDao userAwardRecordDao; // 用户奖品记录数据访问层
    @Resource
    private IUserRaffleOrderDao userRaffleOrderDao; // 用户抽奖订单数据访问层
    @Resource
    private IAwardDao awardDao; // 奖品数据访问层，处理奖品相关查询和更新
    @Resource
    private IUserCreditAccountDao userCreditAccountDao; // 用户积分账户数据访问层
    @Resource
    private IDBRouterStrategy dbRouter; // 数据库路由策略
    @Resource
    private TransactionTemplate transactionTemplate; // 事务模板
    @Resource
    private EventPublisher eventPublisher; // 事件发布器，用于发送消息
    @Resource
    private IRedisService redisService; // Redis 服务接口

    /**
     * 保存用户奖品记录
     * 该方法将用户奖品记录和任务数据存储到数据库，同时发布奖品消息到消息队列。
     * 1. 在事务中保存奖品记录和任务记录，确保数据一致性。
     * 2. 在事务外发布消息，如果失败，则任务会被补偿机制处理。
     *
     * @param userAwardRecordAggregate 聚合对象，包含奖品记录和任务记录
     */
    @Override
    public void saveUserAwardRecord(UserAwardRecordAggregate userAwardRecordAggregate) {
        // 从聚合对象中提取奖品记录和任务记录
        UserAwardRecordEntity userAwardRecordEntity = userAwardRecordAggregate.getUserAwardRecordEntity();
        TaskEntity taskEntity = userAwardRecordAggregate.getTaskEntity();
        String userId = userAwardRecordEntity.getUserId();
        Long activityId = userAwardRecordEntity.getActivityId();
        Integer awardId = userAwardRecordEntity.getAwardId();

        // 转换奖品记录为数据库对象
        UserAwardRecord userAwardRecord = new UserAwardRecord();
        userAwardRecord.setUserId(userAwardRecordEntity.getUserId());
        userAwardRecord.setActivityId(userAwardRecordEntity.getActivityId());
        userAwardRecord.setStrategyId(userAwardRecordEntity.getStrategyId());
        userAwardRecord.setOrderId(userAwardRecordEntity.getOrderId());
        userAwardRecord.setAwardId(userAwardRecordEntity.getAwardId());
        userAwardRecord.setAwardTitle(userAwardRecordEntity.getAwardTitle());
        userAwardRecord.setAwardTime(userAwardRecordEntity.getAwardTime());
        userAwardRecord.setAwardState(userAwardRecordEntity.getAwardState().getCode());

        // 转换任务记录为数据库对象
        Task task = new Task();
        task.setUserId(taskEntity.getUserId());
        task.setTopic(taskEntity.getTopic());
        task.setMessageId(taskEntity.getMessageId());
        task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
        task.setState(taskEntity.getState().getCode());

        // 构建用户抽奖单对象，用于更新状态为“已使用”
        UserRaffleOrder userRaffleOrderReq = new UserRaffleOrder();
        userRaffleOrderReq.setUserId(userAwardRecordEntity.getUserId());
        userRaffleOrderReq.setOrderId(userAwardRecordEntity.getOrderId());

        try {
            // 设置数据库路由
            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                    // 1. 保存奖品记录
                    userAwardRecordDao.insert(userAwardRecord);
                    // 2. 保存任务记录
                    taskDao.insert(task);
                    // 3. 更新抽奖单状态为“已使用”
                    int count = userRaffleOrderDao.updateUserRaffleOrderStateUsed(userRaffleOrderReq);
                    if (1 != count) {
                        // 如果更新失败（受影响行数不为1），则回滚事务并记录错误日志
                        status.setRollbackOnly(); // 回滚事务
                        log.error("写入中奖记录，用户抽奖单已使用过，不可重复抽奖 userId: {} activityId: {} awardId: {}", userId, activityId, awardId);
                        throw new AppException(ResponseCode.ACTIVITY_ORDER_ERROR.getCode(), ResponseCode.ACTIVITY_ORDER_ERROR.getInfo());
                    }
                    return 1;
                } catch (DuplicateKeyException e) {
                    // 处理唯一键冲突，回滚事务
                    status.setRollbackOnly();
                    log.error("写入中奖记录，唯一索引冲突 userId: {} activityId: {} awardId: {}", userId, activityId, awardId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            // 清除数据库路由
            dbRouter.clear();
        }

        // 发布消息到消息队列（事务外操作）
        // TODO 可以用线程池的方式实现，让它快速结束，就发一下，发成功就成功，失败就用任务扫描再发送
        try {
            // 发送消息【在事务外执行，如果失败还有任务补偿】
            eventPublisher.publish(task.getTopic(), task.getMessage());
            // 消息发送成功，更新任务状态为已完成
            taskDao.updateTaskSendMessageCompleted(task);
            log.info("写入中奖记录，发送MQ消息完成 userId: {} orderId: {} topic: {}", userId, userAwardRecordEntity.getOrderId(), task.getTopic());
        } catch (Exception e) {
            // 消息发送失败，更新任务状态为失败
            log.error("写入任务记录，发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
            taskDao.updateTaskSendMessageFail(task);
        }

    }

    /**
     * 查询奖品的配置内容
     *
     * @param awardId 奖品ID，用于标识具体的奖品
     * @return 返回奖品的配置内容，通常是JSON字符串或配置值，未找到可能返回null
     */
    @Override
    public String queryAwardConfig(Integer awardId) {
        return awardDao.queryAwardConfigByAwardId(awardId);
    }

    /**
     * 保存发奖的聚合对象
     *
     * @param giveOutPrizesAggregate 发奖的聚合对象，包含用户ID、奖品记录实体和积分奖品实体
     */
    @Override
    public void saveGiveOutPrizesAggregate(GiveOutPrizesAggregate giveOutPrizesAggregate) {
        String userId = giveOutPrizesAggregate.getUserId();
        UserAwardRecordEntity userAwardRecordEntity = giveOutPrizesAggregate.getUserAwardRecordEntity();
        UserCreditAwardEntity userCreditAwardEntity = giveOutPrizesAggregate.getUserCreditAwardEntity();

        // 更新发奖记录
        UserAwardRecord userAwardRecordReq = new UserAwardRecord();
        userAwardRecordReq.setUserId(userId);
        userAwardRecordReq.setOrderId(userAwardRecordEntity.getOrderId());
        userAwardRecordReq.setAwardState(userAwardRecordEntity.getAwardState().getCode());

        // 更新用户积分「首次则插入数据」
        UserCreditAccount userCreditAccountReq = new UserCreditAccount();
        userCreditAccountReq.setUserId(userCreditAwardEntity.getUserId());
        userCreditAccountReq.setTotalAmount(userCreditAwardEntity.getCreditAmount());
        userCreditAccountReq.setAvailableAmount(userCreditAwardEntity.getCreditAmount());
        userCreditAccountReq.setAccountStatus(AccountStatusVO.open.getCode());

        // 获取分布式锁，确保并发环境下的唯一性
        RLock lock = redisService.getLock(Constants.RedisKey.ACTIVITY_ACCOUNT_LOCK + userId);
        try {
            // 设置锁的过期时间为3秒
            lock.lock(3, TimeUnit.SECONDS);

            // 设置数据库路由
            dbRouter.doRouter(userId);
            transactionTemplate.execute(status -> {
                try {
                    // 查询用户积分账户，如果不存在则插入新账户，否则更新账户余额
                    UserCreditAccount userCreditAccountRes = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
                    if (null == userCreditAccountRes) {
                        userCreditAccountDao.insert(userCreditAccountReq);
                    } else {
                        userCreditAccountDao.updateAddAmount(userCreditAccountReq);
                    }

                    // 更新中奖记录，确保状态一致
                    int updateAwardCount = userAwardRecordDao.updateAwardRecordCompletedState(userAwardRecordReq);
                    if (0 == updateAwardCount) {
                        log.warn("更新中奖记录，重复更新拦截 userId: {} giveOutPrizesAggregate: {}", userId, JSON.toJSONString(giveOutPrizesAggregate));
                        status.setRollbackOnly();
                    }
                    return 1; // 返回成功标志
                } catch (DuplicateKeyException e) {
                    // 处理唯一键冲突，回滚事务
                    status.setRollbackOnly();
                    log.error("更新中奖记录，唯一索引冲突 userId: {}", userId, e);
                    throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
                }
            });
        } finally {
            // 清除数据库路由并释放锁
            dbRouter.clear();
            lock.unlock();
        }
    }

    /**
     * 查询奖品的关键配置
     *
     * @param awardId 奖品ID，用于标识具体的奖品
     * @return 返回奖品的关键配置Key
     */
    @Override
    public String queryAwardKey(Integer awardId) {
        return awardDao.queryAwardKeyByAwardId(awardId);
    }

}
